// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

using System;
using System.IO;
using System.Security.Cryptography;
using System.ServiceModel.Channels;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace SL.IdentityModel.Protocols.WSTrust
{
    /// <summary>
    /// Class inherits from MessageHeader, is used to add an issued token into the security header of SOAP message
    /// </summary>
    public class IssuedTokenHeader : MessageHeader
    {
        private static class TimestampConstants
        {
            public static XNamespace NamespaceUri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
            public const string Timestamp = "Timestamp";
            public const string IdAttribute = "Id";
            public const string Created = "Created";
            public const string Expires = "Expires";
            public const string DateTimeStringFormat = "yyyy-MM-ddTHH:mm:ss.fffZ";
        }

        private static class SignatureConstants
        {
            public static XNamespace NamespaceUri = "http://www.w3.org/2000/09/xmldsig#";
            public const string Algorithm = "Algorithm";
            public const string CanonicalAlgorithmValue = "http://www.w3.org/2001/10/xml-exc-c14n#";
            public const string CanonicalizationMethod = "CanonicalizationMethod";
            public const string DigestMethod = "DigestMethod";
            public const string DigestAlgorithmValue = "http://www.w3.org/2000/09/xmldsig#sha1";
            public const string DigestValue = "DigestValue";
            public const string KeyInfo = "KeyInfo";
            public const string Reference = "Reference";
            public const string SignAlgorithmValue = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
            public const string Signature = "Signature";
            public const string SignedInfo = "SignedInfo";
            public const string SignatureMethod = "SignatureMethod";
            public const string SignatureValue = "SignatureValue";
            public const string Transform = "Transform";
            public const string Transforms = "Transforms";
            public const string URI = "URI";
        }

        private RequestSecurityTokenResponse _rstr = null;

        public override string Name
        {
            get { return "Security"; }
        }

        public override string Namespace
        {
            get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
        }

        /// <summary>
        /// Creates an instance of the class
        /// </summary>
        /// <param name="issuedToken">sets issued token to be used in the SOAP header</param>
        public IssuedTokenHeader( RequestSecurityTokenResponse rstr )
        {
            if ( null == rstr )
            {
                throw new ArgumentNullException( "rstr" );
            }
            _rstr = rstr;
        }

        protected override void OnWriteHeaderContents( XmlDictionaryWriter writer, MessageVersion messageVersion )
        {
            writer.WriteNode( XmlReader.Create( new StringReader( _rstr.RequestedSecurityToken.RawToken ) ), false );

            if ( String.Equals( _rstr.KeyType, WSTrust13Constants.KeyTypes.Symmetric ) )
            {
                XElement timestamp = CreateTimestampNode( "_0" );
                timestamp.WriteTo( writer );
                byte[] canonizedBytes = Encoding.UTF8.GetBytes( timestamp.ToString( SaveOptions.DisableFormatting ) );
                XElement signature = CreateSignatureNode( "_0", canonizedBytes, _rstr.SymmetricProofTokenBytes, _rstr.ProofTokenReference );
                signature.WriteTo( writer );
            }
        }

        private static XElement CreateTimestampNode( string id )
        {
            XNamespace ns = TimestampConstants.NamespaceUri;

            DateTime now = DateTime.UtcNow;
            DateTime expiration = now.AddMinutes( 5 );

            XElement elem = new XElement( ns + TimestampConstants.Timestamp,
                                         new XAttribute( XNamespace.Xmlns + "u", ns ),
                                         new XAttribute( ns + TimestampConstants.IdAttribute, id ),
                                         new XElement( ns + TimestampConstants.Created, now.ToString( TimestampConstants.DateTimeStringFormat ) ),
                                         new XElement( ns + TimestampConstants.Expires, expiration.ToString( TimestampConstants.DateTimeStringFormat ) ) );


            return elem;
        }

        private static XElement CreateSignatureNode( string referenceId, byte[] referenceBytes, byte[] keyBytes, XNode KeyInfoReference )
        {
            if ( string.IsNullOrEmpty( referenceId ) )
            {
                throw new ArgumentNullException( "referenceId" );
            }

            if ( null == referenceBytes )
            {
                throw new ArgumentNullException( "referenceBytes" );
            }

            if ( null == keyBytes )
            {
                throw new ArgumentNullException( "keyBytes" );
            }

            XNamespace ns = SignatureConstants.NamespaceUri;

            string digestValue;
            using ( SHA1 sha1 = new SHA1Managed() )
            {
                digestValue = Convert.ToBase64String( sha1.ComputeHash( referenceBytes ) );
            }

            XElement elem = new XElement( ns + SignatureConstants.Signature,
                new XElement( ns + SignatureConstants.SignedInfo,
                    new XElement( ns + SignatureConstants.CanonicalizationMethod,
                        new XAttribute( SignatureConstants.Algorithm, SignatureConstants.CanonicalAlgorithmValue ),
                        "" ),
                    new XElement( ns + SignatureConstants.SignatureMethod,
                        new XAttribute( SignatureConstants.Algorithm, SignatureConstants.SignAlgorithmValue ),
                        "" ),
                    new XElement( ns + SignatureConstants.Reference,
                        new XAttribute( SignatureConstants.URI, "#" + referenceId ),
                        new XElement( ns + SignatureConstants.Transforms,
                            new XElement( ns + SignatureConstants.Transform,
                                new XAttribute( SignatureConstants.Algorithm, SignatureConstants.CanonicalAlgorithmValue ),
                                "" ) ),
                        new XElement( ns + SignatureConstants.DigestMethod,
                            new XAttribute( SignatureConstants.Algorithm, SignatureConstants.DigestAlgorithmValue ),
                            "" ),
                        new XElement( ns + SignatureConstants.DigestValue, digestValue ) ) ) );

            string canonizedString = elem.FirstNode.ToString( SaveOptions.DisableFormatting );
            byte[] canonizedBytes = Encoding.UTF8.GetBytes( canonizedString );
            string signatureValue;
            using ( HMACSHA1 hmacsha1 = new HMACSHA1( keyBytes ) )
            {
                signatureValue = Convert.ToBase64String( hmacsha1.ComputeHash( canonizedBytes ) );
            }


            elem.Add( new XElement( ns + SignatureConstants.SignatureValue, signatureValue ) );
            if ( null != KeyInfoReference )
            {
                elem.Add( new XElement( ns + SignatureConstants.KeyInfo, KeyInfoReference ) );
            }

            return elem;
        }

    }
}